defmodule Pango.DeeprPgFilter do
	@moduledoc false
	import PrivateCandy
	@pg_param :pg_param
	require Logger

	@doc """
	json_fields: 传了 jsonb_array_elements(xxx)p 的话, 就是 xxx 的json属性
	"""
	def where(condition, fields, query_params \\ {1, []}) do
		Logger.debug("pg filter: #{inspect(condition)}")
		{order, condition} = Map.pop(condition, "OrderBy", %{})
		{group_by, condition} = Map.pop(condition, "GroupBy", [])
		order = order_by(order)

		group_by =
			if group_by != [] do
				" GROUP BY " <> Enum.join(group_by, ", ")
			else
				""
			end
		EnumKit.reduce_map_join(
			condition,
			" AND ",
			query_params,
			fn
				# func(where, index, params_acc)::{str, {index, params_acc}}
				{func, where}, {index, params_acc} when is_function(func) ->
					func.(where, index, params_acc)
				{o, where}, acc when o in ["OR", "or"] ->
					if map_size(where) != 0 do
						match("OR", where, fields, acc)
					else
						:skip
					end
				{operator, where}, {index_acc, params_acc} = acc when is_map(where) ->
					case Map.get(fields, operator) do
						%{type: :jsonb} ->
							value = DeeprConvert.lookup_convert(fields, operator, where)
							{str, acc} = update_pg_param_info(index_acc, params_acc, value)
							{operator <> " @> " <> str, acc}
						%{type: :foreign, mod: mod, for: for} ->
							prefix = Process.get(:prefix)
							table = PsqlLib.get_table(prefix, mod)
							pk = mod.primary_key()
							{f, acc} = where(where, mod.fields, acc)
							str = for <> "=any(select #{pk} from #{table} #{f}) "
							{str, acc}
						_ ->
							if map_size(where) != 0 do
								match(operator, " AND ", where, fields, acc)
							else
								:skip
							end
					end
				{key, value}, acc ->
					key = decode_key(key)
					join(key, value, fields, acc)
				_, _ ->
					PrivateCandy.debug_remind(__ENV__, "条件错误: condition:#{inspect(condition)}")
			end
		)
		|> case do
				 {"", acc} ->
					 {group_by <> order, acc}
				 {sql, acc} ->
					 {" WHERE " <> sql <> group_by <> order, acc}
			 end
	end

	# %{"OR" => [v1 = %{}, v2 = %{}]}, v1, v2 用 OR 连接
	# %{"OR" => %{"key" => v}} 用 OR 连接
	def match("OR", where, fields, acc) do
		EnumKit.reduce_map_join(
			where,
			" OR ",
			acc,
			fn
				#				{oper, value}, acc2 when oper in ["like", ">=", ">", "<", "<=", "to_date", "between"] ->
				#					match(oper, " OR ", value, fields, acc2)
				{key, value}, acc2 ->
					join(key, value, fields, acc2)
				value, acc2 when is_map(value) ->
					{str, acc2} = EnumKit.reduce_map_join(value, " AND ", acc2, &join(elem(&1, 0), elem(&1, 1), fields, &2))
					{"(" <> str <> ")", acc2}
			end
		)
		|> case do
				 {"", acc2} ->
					 {"", acc2}
				 {str, acc2} ->
					 {"(" <> str <> ")", acc2}
			 end
	end
	def match(o, _, where, fields, acc) when o in ["BETWEEN", "between"] do
		EnumKit.reduce_map_join(
			where,
			" AND ",
			acc,
			fn {key, [min, max]}, acc ->
				is_binary(key) || debug_remind(__ENV__, "BETWEEN key 类型错误,只能为string, key:#{key}")
				{min, max} = case Map.get(fields, key) do
					%{type: :timestamp} ->
						min = TimeLib.string_to_date(min) || PrivateCandy.debug_remind(__ENV__, "#{inspect(min)} not is date")
						max = TimeLib.string_to_date(max) || PrivateCandy.debug_remind(__ENV__, "#{inspect(max)} not is date")
						{min, max}
					%{type: :date} ->
						min = DateLib.from_iso!(min)
						max = DateLib.from_iso!(max)
						{min, max}
					_ ->
						{min, max}
				end
				{v1, acc} = update_pg_param_info(acc, min)
				{v2, acc} = update_pg_param_info(acc, max)
				{key <> " BETWEEN " <> v1 <> " AND " <> v2, acc}
			end
		)
	end
	def match("to_date", joiner, where, fields, acc) do
		EnumKit.reduce_map_join(
			where,
			joiner,
			acc,
			fn ({key, value}, acc) ->
				concat_value(fields, key, acc, "to_char(#{key},'YYYY-MM-DD')=", value, false, true)
			end
		)
	end
	def match(operator, joiner, where, fields, acc) do
		String.contains?(operator, ["--", "or", "true", " "]) &&
			PrivateCandy.debug_remind(__ENV__, "#{operator}, input_error")
		operator = if operator not in ["like", ">", "<", ">=", "<=", "<>"] do
			operator <> "="
		else
			operator
		end
		EnumKit.reduce_map_join(where, joiner, acc, &get_filed_v(fields, elem(&1, 0), elem(&1, 1), &2, operator))
	end


	def join(key, value, fields, acc) do
		get_filed_v(fields, key, value, acc)
	end


	defp get_filed_v(fields, key, value, acc, operator1 \\ "=") do
		value = DeeprConvert.lookup_convert(fields, key, value)
		operator = " " <> operator1 <> " "
		is_equal = operator1 == "="
		key = decode_key(key)
		if is_list(key) do
			{str, acc} = EnumKit.reduce_map_join(key, " OR ", acc, &get_filed_v(fields, &1, value, &2, operator1))
			{"(" <> str <> ")", acc}
		else
			is_like = operator1 == "like"
			case String.split(key, "->") do
				[key, filed] ->
					json_filter(fields, key, nil, filed, value, operator, is_like, is_equal, acc)
				[key, filed1, filed2] ->
					json_filter(fields, key, filed1, filed2, value, operator, is_like, is_equal, acc)
				_ ->
					{index_acc, params_acc} = acc
					case Map.get(fields, key) do
						nil ->
							PrivateCandy.debug_remind(__ENV__, "#{inspect(key)} not exists")
						%{type: :jsonb} ->
							{str, acc} = update_pg_param_info(index_acc, params_acc, value)
							{key <> "@>#{str}", acc}
						%{type: {:array, _}} ->
							{str, acc} = update_pg_param_info(index_acc, params_acc, value)
							{str <> "=ANY(#{key})", acc}
						%{type: type} ->
							#							value = case type do
							#								:date ->
							#									if is_list(value) do
							#										Enum.map(value, &DateLib.from_iso!(&1))
							#									else
							#										DateLib.from_iso!(value)
							#									end
							#								:timestamp ->
							#									if is_list(value) do
							#										Enum.map(value, &(NaiveDateTime.from_iso8601!(&1)))
							#									else
							#										case NaiveDateTime.from_iso8601(value) do
							#											{:ok, v} ->
							#												v
							#											{:error, _} ->
							#												PrivateCandy.debug_remind(__ENV__, "#{key}, #{value} input_error")
							#										end
							#									end
							#								_ ->
							#									value
							#							end
							str = key <> operator
							concat_value(fields, key, acc, str, value, is_like, is_equal)
					end
			end
		end
	end


	defp json_filter(fields, key, pre_field, filed, value, operator, is_like, is_equal, acc) do
		{index_acc, params_acc} = acc
		case !is_list(value) && pre_field == nil && is_equal && Map.get(fields, key) do
			nil ->
				PrivateCandy.debug_remind(__ENV__, "#{inspect(key)} not exists")
			%{default: []} ->
				v = %{filed => value}
				value = DeeprConvert.lookup_convert(fields, key, v)
				{str, acc} = update_pg_param_info(index_acc, params_acc, [value])
				{key <> "@>" <> str, acc}
			%{default: %{}} ->
				v = %{filed => value}
				value = DeeprConvert.lookup_convert(fields, key, v)
				{str, acc} = update_pg_param_info(index_acc, params_acc, value)
				{key <> "@>" <> str, acc}
			_ ->
				case Map.get(fields, key) do
					%{type: :foreign, mod: mod, for: key} ->
						db_type = mod.db_type()
						prefix = if mod.is_global() do
							nil
						else
							Process.get(:prefix)
						end
						if db_type == Pango.DbType.pg() do
							table = PsqlLib.get_table(prefix, mod)
							pk = mod.primary_key()
							value = DeeprConvert.lookup_convert(mod.fields(), filed, value)
							{str, acc} = concat_value(mod.fields(), filed, acc, filed <> operator, value, is_like, is_equal)
							str = "#{key} in (select #{pk} from #{table} where " <> str <> ")"
							{str, acc}
						else
							PrivateCandy.debug_remind(__ENV__, "暂不支持垮库查询")
						end
					_ ->
						str = if pre_field != nil do
							key <> "->" <> pre_field <> "->>'" <> filed <> "'" <> operator
						else
							key <> "->>'" <> filed <> "'" <> operator
						end
						concat_value(fields, key, acc, str, value, is_like, is_equal)
				end
		end
	end


	def concat_value(fields, field, acc, str, value, is_like, is_equal) do
		if !is_equal do
			if is_list(value) do
				{str, acc} = EnumKit.reduce_map_join(
					value,
					" OR ",
					acc,
					fn (h, acc2) ->
						h = like_value_convert(is_like, fields, field, h)
						{str2, acc2} = update_pg_param_info(acc2, h)
						{str <> str2, acc2}
					end
				)
				{"(" <> str <> ")", acc}
			else
				value = like_value_convert(is_like, fields, field, value)
				{str2, acc2} = update_pg_param_info(acc, value)
				{str <> str2, acc2}
			end
		else
			case Map.get(fields, field) do
				%{type: :jsonb} ->
					{str2, acc2} = update_pg_param_info(acc, value)
					{field <> "@>#{str2}", acc2}
				_ ->
					if is_list(value) do
						{str2, acc2} = update_pg_param_info(acc, value)
						{str <> "any(#{str2})", acc2}
					else
						!is_binary(value) || (String.trim(value) == "" && PrivateCandy.debug_remind(__ENV__, "#{str} input_error"))
						{str2, acc2} = update_pg_param_info(acc, value)
						{str <> str2, acc2}
					end
			end
		end
	end

	defp like_value_convert(is_like, fields, field, value) do
		if is_like do
			(value != "" && !String.contains?(value, "%")) || PrivateCandy.debug_remind(__ENV__, "#{value} contains %")
			case MapLib.get_in(fields, [field, :like], 1) do
				1 ->
					value <> "%"
				2 ->
					"%" <> value <> "%"
			end
		else
			value
		end
	end

	def order_by(order) do
		Enum.map_join(
			order,
			",  ",
			fn ({key, str}) ->
				str in ["ASC", "DESC"] ||
					PrivateCandy.debug_remind(__ENV__, "Order Arg, order: #{inspect(order)}")
				key <> " " <> str
			end
		)
		|> case do
				 "" ->
					 ""
				 str ->
					 " ORDER BY " <> str
			 end
	end


	@doc """
	更新参数化查询 index, 返回旧的
	"""
	def update_pg_param_info({index, params}, v) do
		update_pg_param_info(index, params, v)
	end
	def update_pg_param_info(index, params, v) do
		{"$#{index}", {index + 1, [v | params]}}
	end

	defp decode_key(key) do
		case Jason.decode(key) do
			{:ok, v} when is_list(v) ->
				v
			_ ->
				key
		end
	end


end
